/* packet-gearman.c
 * Routines for Gearman protocol packet disassembly
 * By Flier Lu <flier.lu@gmail.com>
 * Copyright 2010 Flier Lu
 *
 * Gearman Protocol
 * ----------------
 * http://gearman.org/index.php?id=protocol
 *
 * $Id$
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include <string.h>
#include <stdio.h>

#include <epan/packet.h>

/*
 * No public information available.
 */

static int proto_gearman = -1;

static int hf_gearman_mgr_cmd = -1;
static int hf_gearman_magic_code = -1;
static int hf_gearman_pkt_type = -1;
static int hf_gearman_data_size = -1;
static int hf_gearman_data_content = -1;
static int hf_gearman_option_name = -1;
static int hf_gearman_func_name = -1;
static int hf_gearman_func_namez = -1;
static int hf_gearman_timeout = -1;
static int hf_gearman_client_id = -1;
static int hf_gearman_uniq_id = -1;
static int hf_gearman_argument = -1;
static int hf_gearman_job_handle = -1;
static int hf_gearman_job_handlez = -1;
static int hf_gearman_complete_numerator = -1;
static int hf_gearman_complete_denominator = -1;
static int hf_gearman_result = -1;
static int hf_gearman_known_status = -1;
static int hf_gearman_running_status = -1;
static int hf_gearman_echo_text = -1;
static int hf_gearman_err_code = -1;
static int hf_gearman_err_text = -1;

static gint ett_gearman = -1;
static gint ett_gearman_command = -1;
static gint ett_gearman_content = -1;

const int GEARMAN_COMMAND_HEADER_SIZE = 12;
const int GEARMAN_PORT = 4730;
const gchar *GEARMAN_MAGIC_CODE_REQUEST = "\0REQ";
const gchar *GEARMAN_MAGIC_CODE_RESPONSE = "\0RES";

const gchar *GEARMAN_MGR_CMDS[] = {
  "workers",
  "status",
  "maxqueue",
  "shutdown",
  "version"
};

const int GEARMAN_MGR_CMDS_COUNT = sizeof(GEARMAN_MGR_CMDS)/sizeof(GEARMAN_MGR_CMDS[0]);

typedef enum
{
  GEARMAN_COMMAND_TEXT,
  GEARMAN_COMMAND_CAN_DO,              /* W->J: FUNC */
  GEARMAN_COMMAND_CANT_DO,             /* W->J: FUNC */
  GEARMAN_COMMAND_RESET_ABILITIES,     /* W->J: -- */
  GEARMAN_COMMAND_PRE_SLEEP,           /* W->J: -- */
  GEARMAN_COMMAND_UNUSED,
  GEARMAN_COMMAND_NOOP,                /* J->W: -- */
  GEARMAN_COMMAND_SUBMIT_JOB,          /* C->J: FUNC[0]UNIQ[0]ARGS */
  GEARMAN_COMMAND_JOB_CREATED,         /* J->C: HANDLE  */
  GEARMAN_COMMAND_GRAB_JOB,            /* W->J: --  */
  GEARMAN_COMMAND_NO_JOB,              /* J->W: -- */
  GEARMAN_COMMAND_JOB_ASSIGN,          /* J->W: HANDLE[0]FUNC[0]ARG  */
  GEARMAN_COMMAND_WORK_STATUS,         /* W->J/C: HANDLE[0]NUMERATOR[0]DENOMINATOR */
  GEARMAN_COMMAND_WORK_COMPLETE,       /* W->J/C: HANDLE[0]RES */
  GEARMAN_COMMAND_WORK_FAIL,           /* W->J/C: HANDLE */
  GEARMAN_COMMAND_GET_STATUS,          /* C->J: HANDLE */
  GEARMAN_COMMAND_ECHO_REQ,            /* ?->J: TEXT */
  GEARMAN_COMMAND_ECHO_RES,            /* J->?: TEXT */
  GEARMAN_COMMAND_SUBMIT_JOB_BG,       /* C->J: FUNC[0]UNIQ[0]ARGS  */
  GEARMAN_COMMAND_ERROR,               /* J->?: ERRCODE[0]ERR_TEXT */
  GEARMAN_COMMAND_STATUS_RES,          /* C->J: HANDLE[0]KNOWN[0]RUNNING[0]NUM[0]DENOM */
  GEARMAN_COMMAND_SUBMIT_JOB_HIGH,     /* C->J: FUNC[0]UNIQ[0]ARGS  */
  GEARMAN_COMMAND_SET_CLIENT_ID,       /* W->J: [RANDOM_STRING_NO_WHITESPACE] */
  GEARMAN_COMMAND_CAN_DO_TIMEOUT,      /* W->J: FUNC[0]TIMEOUT */
  GEARMAN_COMMAND_ALL_YOURS,
  GEARMAN_COMMAND_WORK_EXCEPTION,
  GEARMAN_COMMAND_OPTION_REQ,
  GEARMAN_COMMAND_OPTION_RES,
  GEARMAN_COMMAND_WORK_DATA,
  GEARMAN_COMMAND_WORK_WARNING,
  GEARMAN_COMMAND_GRAB_JOB_UNIQ,
  GEARMAN_COMMAND_JOB_ASSIGN_UNIQ,
  GEARMAN_COMMAND_SUBMIT_JOB_HIGH_BG,
  GEARMAN_COMMAND_SUBMIT_JOB_LOW,
  GEARMAN_COMMAND_SUBMIT_JOB_LOW_BG,
  GEARMAN_COMMAND_SUBMIT_JOB_SCHED,
  GEARMAN_COMMAND_SUBMIT_JOB_EPOCH,
  GEARMAN_COMMAND_MAX /* Always add new commands before this. */
} gearman_command_t;

static const value_string gearman_command_names[] = {
  { GEARMAN_COMMAND_TEXT,               "TEXT" },
  { GEARMAN_COMMAND_CAN_DO,             "CAN_DO" },             /* W->J: FUNC */
  { GEARMAN_COMMAND_CANT_DO,            "CANT_DO" },            /* W->J: FUNC */
  { GEARMAN_COMMAND_RESET_ABILITIES,    "RESET_ABILITIES" },    /* W->J: -- */
  { GEARMAN_COMMAND_PRE_SLEEP,          "PRE_SLEEP" },          /* W->J: -- */
  { GEARMAN_COMMAND_UNUSED,             "UNUSED" },
  { GEARMAN_COMMAND_NOOP,               "NOOP" },               /* J->W: -- */
  { GEARMAN_COMMAND_SUBMIT_JOB,         "SUBMIT_JOB" },         /* C->J: FUNC[0]UNIQ[0]ARGS */
  { GEARMAN_COMMAND_JOB_CREATED,        "JOB_CREATED" },        /* J->C: HANDLE  */
  { GEARMAN_COMMAND_GRAB_JOB,           "GRAB_JOB" },           /* W->J: --  */
  { GEARMAN_COMMAND_NO_JOB,             "NO_JOB" },             /* J->W: -- */
  { GEARMAN_COMMAND_JOB_ASSIGN,         "JOB_ASSIGN" },         /* J->W: HANDLE[0]FUNC[0]ARG  */
  { GEARMAN_COMMAND_WORK_STATUS,        "WORK_STATUS" },        /* W->J/C: HANDLE[0]NUMERATOR[0]DENOMINATOR */
  { GEARMAN_COMMAND_WORK_COMPLETE,      "WORK_COMPLETE" },      /* W->J/C: HANDLE[0]RES */
  { GEARMAN_COMMAND_WORK_FAIL,          "WORK_FAIL" },          /* W->J/C: HANDLE */
  { GEARMAN_COMMAND_GET_STATUS,         "GET_STATUS" },         /* C->J: HANDLE */
  { GEARMAN_COMMAND_ECHO_REQ,           "ECHO_REQ" },           /* ?->J: TEXT */
  { GEARMAN_COMMAND_ECHO_RES,           "ECHO_RES" },           /* J->?: TEXT */
  { GEARMAN_COMMAND_SUBMIT_JOB_BG,      "SUBMIT_JOB_BG" },      /* C->J: FUNC[0]UNIQ[0]ARGS  */
  { GEARMAN_COMMAND_ERROR,              "ERROR" },              /* J->?: ERRCODE[0]ERR_TEXT */
  { GEARMAN_COMMAND_STATUS_RES,         "STATUS_RES" },         /* C->J: HANDLE[0]KNOWN[0]RUNNING[0]NUM[0]DENOM */
  { GEARMAN_COMMAND_SUBMIT_JOB_HIGH,    "SUBMIT_JOB_HIGH" },    /* C->J: FUNC[0]UNIQ[0]ARGS  */
  { GEARMAN_COMMAND_SET_CLIENT_ID,      "SET_CLIENT_ID" },      /* W->J: [RANDOM_STRING_NO_WHITESPACE] */
  { GEARMAN_COMMAND_CAN_DO_TIMEOUT,     "CAN_DO_TIMEOUT" },     /* W->J: FUNC[0]TIMEOUT */
  { GEARMAN_COMMAND_ALL_YOURS,          "ALL_YOURS" },
  { GEARMAN_COMMAND_WORK_EXCEPTION,     "WORK_EXCEPTION" },
  { GEARMAN_COMMAND_OPTION_REQ,         "OPTION_REQ" },
  { GEARMAN_COMMAND_OPTION_RES,         "OPTION_RES" },
  { GEARMAN_COMMAND_WORK_DATA,          "WORK_DATA" },
  { GEARMAN_COMMAND_WORK_WARNING,       "WORK_WARNING" },
  { GEARMAN_COMMAND_GRAB_JOB_UNIQ,      "GRAB_JOB_UNIQ" },
  { GEARMAN_COMMAND_JOB_ASSIGN_UNIQ,    "JOB_ASSIGN_UNIQ" },
  { GEARMAN_COMMAND_SUBMIT_JOB_HIGH_BG, "SUBMIT_JOB_HIGH_BG" },
  { GEARMAN_COMMAND_SUBMIT_JOB_LOW,     "SUBMIT_JOB_LOW" },
  { GEARMAN_COMMAND_SUBMIT_JOB_LOW_BG,  "SUBMIT_JOB_LOW_BG" },
  { GEARMAN_COMMAND_SUBMIT_JOB_SCHED,   "SUBMIT_JOB_SCHED" },
  { GEARMAN_COMMAND_SUBMIT_JOB_EPOCH,   "SUBMIT_JOB_EPOCH" },
  { 0, NULL}
};

void dissect_binary_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *gearman_tree);

void dissect_management_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *gearman_tree);
                                        
void                             
dissect_gearman(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
  proto_item *ti = NULL;
  proto_tree *gearman_tree = NULL;

  col_set_str(pinfo->cinfo, COL_PROTOCOL, "Gearman");

  col_clear(pinfo->cinfo,COL_INFO);

  if (tree)
  {
    ti = proto_tree_add_item(tree, proto_gearman, tvb, 0, -1, FALSE);

    if (ti)
      gearman_tree = proto_item_add_subtree(ti, ett_gearman);
  }

  if ((0 == tvb_memeql(tvb, 0, GEARMAN_MAGIC_CODE_REQUEST, 4)) ||
      (0 == tvb_memeql(tvb, 0, GEARMAN_MAGIC_CODE_RESPONSE, 4)))
  {
    dissect_binary_packet(tvb, pinfo, gearman_tree);
  }
  else
  {
    dissect_management_packet(tvb, pinfo, gearman_tree);
  }
}

void 
dissect_binary_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *gearman_tree)
{
  gint offset = 0;
  char *magic_code = NULL;
  guint32 type, size;

  while (tvb_length_remaining(tvb, offset) >= GEARMAN_COMMAND_HEADER_SIZE)
  {
    magic_code = tvb_get_string(tvb, offset+1, 3);
    type = tvb_get_ntohl(tvb, offset+4);
    size = tvb_get_ntohl(tvb, offset+8);

    if (offset == 0)
    {
      col_append_fstr(pinfo->cinfo, COL_INFO, "[%s] ", magic_code);
    }
    else
    {
      col_append_fstr(pinfo->cinfo, COL_INFO, ", ");
    }

    col_append_fstr(pinfo->cinfo, COL_INFO, "%s(%d) LEN=%d",
      match_strval(type, gearman_command_names), type, size);

    if (tvb_length_remaining(tvb, offset) >= (gint) (GEARMAN_COMMAND_HEADER_SIZE + size))
    {
      proto_item *command_item = proto_tree_add_text(gearman_tree, tvb, offset, GEARMAN_COMMAND_HEADER_SIZE+size, 
        "[%s] %s(%d) LEN=%d", magic_code, match_strval(type, gearman_command_names), type, size);
      proto_tree *command_tree = proto_item_add_subtree(command_item, ett_gearman_command);

      proto_tree_add_text(command_tree, tvb, offset, 4, "Magic Code: \\0%s", magic_code);
      proto_tree_add_item(command_tree, hf_gearman_pkt_type, tvb, offset+4, 4, FALSE);
      proto_tree_add_item(command_tree, hf_gearman_data_size, tvb, offset+8, 4, FALSE);  

      if (size > 0)
      {
        proto_item *content_item = proto_tree_add_item(command_tree, hf_gearman_data_content, tvb, offset+GEARMAN_COMMAND_HEADER_SIZE, size, FALSE);
        proto_tree *content_tree = proto_item_add_subtree(content_item, ett_gearman_content);

        if (type == GEARMAN_COMMAND_ECHO_REQ || type == GEARMAN_COMMAND_ECHO_RES)
        {
          proto_tree_add_item(content_tree, hf_gearman_echo_text, tvb, 
            offset+GEARMAN_COMMAND_HEADER_SIZE, size, FALSE);
        }
        else if (type == GEARMAN_COMMAND_ERROR)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_err_code, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_err_text, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_STATUS_RES || 
                 type == GEARMAN_COMMAND_JOB_CREATED || 
                 type == GEARMAN_COMMAND_WORK_FAIL)
        {
          proto_tree_add_item(content_tree, hf_gearman_job_handle, tvb, 
            offset+GEARMAN_COMMAND_HEADER_SIZE, size, FALSE);
        }
        else if (type == GEARMAN_COMMAND_STATUS_RES)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_job_handlez, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_known_status, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_running_status, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_complete_numerator, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_complete_denominator, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_OPTION_REQ || type == GEARMAN_COMMAND_OPTION_RES)
        {
          proto_tree_add_item(content_tree, hf_gearman_option_name, tvb, 
            offset+GEARMAN_COMMAND_HEADER_SIZE, size, FALSE);
        }
        else if (type == GEARMAN_COMMAND_SUBMIT_JOB || type == GEARMAN_COMMAND_SUBMIT_JOB_BG ||
                 type == GEARMAN_COMMAND_SUBMIT_JOB_HIGH || type == GEARMAN_COMMAND_SUBMIT_JOB_HIGH_BG || 
                 type == GEARMAN_COMMAND_SUBMIT_JOB_LOW || type == GEARMAN_COMMAND_SUBMIT_JOB_LOW_BG)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_func_name, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_uniq_id, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_argument, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_SUBMIT_JOB_SCHED)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);          

          proto_tree_add_item(content_tree, hf_gearman_func_name, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_uniq_id, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_text(content_tree, tvb, start, len, "Minute: %s", tvb_format_text(tvb, start, len));

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_text(content_tree, tvb, start, len, "Hour: %s", tvb_format_text(tvb, start, len));

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_text(content_tree, tvb, start, len, "Day of Month: %s", tvb_format_text(tvb, start, len));

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_text(content_tree, tvb, start, len, "Month: %s", tvb_format_text(tvb, start, len));

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_text(content_tree, tvb, start, len, "Day of Week: %s", tvb_format_text(tvb, start, len));

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_argument, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_SUBMIT_JOB_EPOCH)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_func_name, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_uniq_id, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_text(content_tree, tvb, start, len, "Epoch Time: %s", tvb_format_text(tvb, start, len));

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_argument, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_JOB_ASSIGN)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_job_handlez, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_func_namez, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_argument, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_JOB_ASSIGN_UNIQ)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_job_handlez, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_func_namez, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_uniq_id, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_argument, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_CAN_DO || type == GEARMAN_COMMAND_CANT_DO)
        {
          proto_tree_add_item(content_tree, hf_gearman_func_name, tvb, 
            offset+GEARMAN_COMMAND_HEADER_SIZE, size, FALSE);
        }
        else if (type == GEARMAN_COMMAND_CAN_DO_TIMEOUT)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_func_namez, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_WORK_DATA || 
                 type == GEARMAN_COMMAND_WORK_WARNING ||
                 type == GEARMAN_COMMAND_WORK_COMPLETE ||
                 type == GEARMAN_COMMAND_WORK_EXCEPTION)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_job_handlez, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_result, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_WORK_STATUS)
        {
          gint start = offset + GEARMAN_COMMAND_HEADER_SIZE;
          guint len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_job_handlez, tvb, start, len, FALSE);

          start += len;
          len = tvb_strsize(tvb, start);

          proto_tree_add_item(content_tree, hf_gearman_complete_numerator, tvb, start, len, FALSE);

          start += len;
          len = offset + GEARMAN_COMMAND_HEADER_SIZE + size - start;

          proto_tree_add_item(content_tree, hf_gearman_complete_denominator, tvb, start, len, FALSE);
        }
        else if (type == GEARMAN_COMMAND_SET_CLIENT_ID)
        {
          proto_tree_add_item(content_tree, hf_gearman_client_id, tvb, 
            offset+GEARMAN_COMMAND_HEADER_SIZE, size, FALSE);
        }
      }    

      offset += 12 + size;
    }
  }
}

void
dissect_management_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *gearman_tree)
{
  int i, type = 0, cmdlen, offset = 0, linelen = 0, next_offset = 0;

  while (tvb_reported_length_remaining(tvb, offset) != 0) 
  {
    linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);

    if (linelen < 0)
      break;

    for (i=0; i<GEARMAN_MGR_CMDS_COUNT; i++)
    {
      cmdlen = strlen(GEARMAN_MGR_CMDS[i]);

      if (cmdlen == linelen && 0 == tvb_strneql(tvb, offset, GEARMAN_MGR_CMDS[i], cmdlen))
      {
        proto_tree_add_item(gearman_tree, hf_gearman_mgr_cmd, tvb, offset, cmdlen, FALSE);
        col_add_fstr(pinfo->cinfo, COL_INFO, "[MGR] %s", tvb_get_string(tvb, offset, linelen));

        type = 1;

        break;
      }
    }

    if (GEARMAN_MGR_CMDS_COUNT == i)
    {
      proto_tree_add_text(gearman_tree, tvb, offset, next_offset - offset, 
        "%s", tvb_format_text(tvb, offset, next_offset - offset));

      if (type == 0)
      {
        col_add_fstr(pinfo->cinfo, COL_INFO, "[MGR] %s", tvb_get_string(tvb, offset, linelen));

        type = -1;
      }
      else
      {
        col_append_sep_fstr(pinfo->cinfo, COL_INFO, ",", "%s", tvb_get_string(tvb, offset, linelen));
      }          
    }

    offset = next_offset;
  }
}

void
proto_register_gearman(void)
{
  static hf_register_info hf[] = {
    { &hf_gearman_mgr_cmd, { "Management Command", "gearamn.mgr_cmd", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_gearman_magic_code, { "Magic Code", "gearman.magic_code", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } },
    { &hf_gearman_pkt_type, { "Packet Type", "gearman.pkt_type", FT_UINT32, BASE_DEC_HEX, VALS(gearman_command_names), 0x0, NULL, HFILL} },
    { &hf_gearman_data_size, { "Data Length", "gearman.data_size", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_data_content, { "Data Content", "gearman.data_size", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_option_name, { "Option Name", "gearman.opt.name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_func_name, { "Function Name", "gearman.func.name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_func_namez, { "Function Name", "gearman.func.name", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_timeout, { "Timeout", "gearman.timeout", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_client_id, { "Client ID", "gearman.client_id", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_uniq_id, { "Unique ID", "gearman.uniq_id", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_argument, { "Function Argument", "gearman.func.arg", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_job_handle, { "Job Handle", "gearman.job.handle", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_job_handlez, { "Job Handle", "gearman.job.handle", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_complete_numerator, { "Complete Numerator", "gearman.numerator", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_complete_denominator, { "Complete Denominator", "gearman.denominator", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_result, { "Function Result", "gearman.func.result", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_known_status, { "Known job", "gearman.job.known", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_running_status, { "Running Job", "gearman.job.running", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_echo_text, { "Echo Text", "gearman.echo_text", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_err_code, { "Error Code", "gearman.err.code", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} },
    { &hf_gearman_err_text, { "Error Text", "gearman.err.text", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL} }
  };

  /* Setup protocol subtree array */
  static gint *ett[] = {
    &ett_gearman,
    &ett_gearman_command,
    &ett_gearman_content
  };

  proto_gearman = proto_register_protocol("Gearman Protocol", "Gearman", "gearman");

  proto_register_field_array(proto_gearman, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));
}

void
proto_reg_handoff_gearman(void)
{
  dissector_handle_t gearman_handle;

  gearman_handle = create_dissector_handle(dissect_gearman, proto_gearman);

  dissector_add("tcp.port", GEARMAN_PORT, gearman_handle);
}
